/*
===========================================================================
Copyright (C) 1999-2005 Id Software, Inc.

This file is part of Quake III Arena source code.

Quake III Arena source code is free software; you can redistribute it
and/or modify it under the terms of the GNU General Public License as
published by the Free Software Foundation; either version 2 of the License,
or (at your option) any later version.

Quake III Arena source code is distributed in the hope that it will be
useful, but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Quake III Arena source code; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
===========================================================================
 */
//
// cg_players.c -- handle the media and animation for player entities
#include "cg_local.h"

char *cg_customSoundNames[MAX_CUSTOM_SOUNDS] = {
	"*death1.wav",
	"*death2.wav",
	"*death3.wav",
	"*jump1.wav",
	"*pain25_1.wav",
	"*pain50_1.wav",
	"*pain75_1.wav",
	"*pain100_1.wav",
	"*falling1.wav",
	"*gasp.wav",
	"*drown.wav",
	"*fall1.wav",
	"*taunt.wav"
};

// leilei - eyes hack

vec3_t headpos;
vec3_t headang;

int enableQ;

/*
================
CG_CustomSound

================
 */
sfxHandle_t CG_CustomSound(int clientNum, const char *soundName) {
	clientInfo_t *ci;
	int i;

	if (soundName[0] != '*') {
		return trap_S_RegisterSound(soundName, qfalse);
	}

	if (clientNum < 0 || clientNum >= MAX_CLIENTS) {
		clientNum = 0;
	}
	ci = &cgs.clientinfo[ clientNum ];

	for (i = 0; i < MAX_CUSTOM_SOUNDS && cg_customSoundNames[i]; i++) {
		if (strequals(soundName, cg_customSoundNames[i])) {
			return ci->sounds[i];
		}
	}

	CG_Error("Unknown custom sound: %s", soundName);
	return 0;
}



/*
=============================================================================

CLIENT INFO

=============================================================================
 */

/*
======================
CG_ParseAnimationFile

Read a configuration file containing animation counts and rates
models/players/visor/animation.cfg, etc
======================
 */
static qboolean CG_ParseAnimationFile(const char *filename, clientInfo_t *ci) {
	char *text_p, *prev;
	int len;
	int i;
	char *token;
	float fps;
	int skip;
	char text[20000];
	fileHandle_t f;
	animation_t *animations;

	animations = ci->animations;

	// load the file
	len = trap_FS_FOpenFile(filename, &f, FS_READ);
	if (len <= 0) {
		return qfalse;
	}
	if (len >= sizeof ( text) - 1) {
		CG_Printf("File %s too long\n", filename);
		trap_FS_FCloseFile(f);
		return qfalse;
	}
	trap_FS_Read(text, len, f);
	text[len] = 0;
	trap_FS_FCloseFile(f);

	// parse the text
	text_p = text;
	skip = 0; // quite the compiler warning

	ci->footsteps = FOOTSTEP_NORMAL;
	VectorClear(ci->headOffset);
	ci->gender = GENDER_MALE;
	ci->fixedlegs = qfalse;
	ci->fixedtorso = qfalse;

	// read optional parameters
	while (1) {
		prev = text_p; // so we can unget
		token = COM_Parse(&text_p);
		if (!token) {
			break;
		}
		if (Q_strequal(token, "footsteps")) {
			token = COM_Parse(&text_p);
			if (!token) {
				break;
			}
			if (Q_strequal(token, "default") || Q_strequal(token, "normal")) {
				ci->footsteps = FOOTSTEP_NORMAL;
			} else if (Q_strequal(token, "boot")) {
				ci->footsteps = FOOTSTEP_BOOT;
			} else if (Q_strequal(token, "flesh")) {
				ci->footsteps = FOOTSTEP_FLESH;
			} else if (Q_strequal(token, "mech")) {
				ci->footsteps = FOOTSTEP_MECH;
			} else if (Q_strequal(token, "energy")) {
				ci->footsteps = FOOTSTEP_ENERGY;
			} else {
				CG_Printf("Bad footsteps parm in %s: %s\n", filename, token);
			}
			continue;
		} else if (Q_strequal(token, "headoffset")) {
			for (i = 0; i < 3; i++) {
				token = COM_Parse(&text_p);
				if (!token) {
					break;
				}
				ci->headOffset[i] = atof(token);
			}
			continue;
		} else if (Q_strequal(token, "eyes")) { // leilei - EYES
			for (i = 0; i < 3; i++) {
				token = COM_Parse(&text_p);
				if (!token) {
					break;
				}
				ci->eyepos[i] = atof(token);
			}
			continue;
		} else if (Q_strequal(token, "sex")) {
			token = COM_Parse(&text_p);
			if (!token) {
				break;
			}
			if (token[0] == 'f' || token[0] == 'F') {
				ci->gender = GENDER_FEMALE;
			} else if (token[0] == 'n' || token[0] == 'N') {
				ci->gender = GENDER_NEUTER;
			} else {
				ci->gender = GENDER_MALE;
			}
			continue;
		} else if (Q_strequal(token, "fixedlegs")) {
			ci->fixedlegs = qtrue;
			continue;
		} else if (Q_strequal(token, "fixedtorso")) {
			ci->fixedtorso = qtrue;
			continue;
		}

		// if it is a number, start parsing animations
		if (token[0] >= '0' && token[0] <= '9') {
			text_p = prev; // unget the token
			break;
		}
		Com_Printf("unknown token '%s' in %s\n", token, filename);
	}

	// read information for each frame
	for (i = 0; i < MAX_ANIMATIONS; i++) {

		token = COM_Parse(&text_p);
		if (!token[0]) {
			if (i >= TORSO_GETFLAG && i <= TORSO_NEGATIVE) {
				animations[i] = animations[TORSO_GESTURE];
				animations[i].reversed = qfalse;
				animations[i].flipflop = qfalse;
				continue;
			}
			// LEILEI - run animation fallback.
			if (i == TORSO_RUN) {
				animations[i] = animations[TORSO_STAND];
				animations[i].reversed = qfalse;
				animations[i].flipflop = qfalse;
				continue;
			}
			if (i == TORSO_RUN2) {
				animations[i] = animations[TORSO_STAND2];
				animations[i].reversed = qfalse;
				animations[i].flipflop = qfalse;
				continue;
			}
			if (i == TORSO_RUN3) {
				animations[i] = animations[TORSO_STAND];
				animations[i].reversed = qfalse;
				animations[i].flipflop = qfalse;
				continue;
			}
			if (i == TORSO_STRAFE) {
				animations[i] = animations[TORSO_STAND];
				animations[i].reversed = qfalse;
				animations[i].flipflop = qfalse;
				continue;
			}
			if (i == TORSO_STAND3) {
				animations[i] = animations[TORSO_STAND];
				animations[i].reversed = qfalse;
				animations[i].flipflop = qfalse;
				continue;
			}
			if (i == TORSO_JUMP) {
				animations[i] = animations[TORSO_STAND];
				animations[i].reversed = qfalse;
				animations[i].flipflop = qfalse;
				continue;
			}
			if (i == TORSO_JUMP2) {
				animations[i] = animations[TORSO_STAND];
				animations[i].reversed = qfalse;
				animations[i].flipflop = qfalse;
				continue;
			}
			if (i == TORSO_JUMP3) {
				animations[i] = animations[TORSO_STAND];
				animations[i].reversed = qfalse;
				animations[i].flipflop = qfalse;
				continue;
			}
			if (i == TORSO_FALL) {
				animations[i] = animations[TORSO_STAND];
				animations[i].reversed = qfalse;
				animations[i].flipflop = qfalse;
				continue;
			}
			if (i == TORSO_FALL2) {
				animations[i] = animations[TORSO_STAND2];
				animations[i].reversed = qfalse;
				animations[i].flipflop = qfalse;
				continue;
			}
			if (i == TORSO_FALL3) {
				animations[i] = animations[TORSO_STAND];
				animations[i].reversed = qfalse;
				animations[i].flipflop = qfalse;
				continue;
			}
			if (i == TORSO_TALK) {
				animations[i] = animations[TORSO_STAND];
				animations[i].reversed = qfalse;
				animations[i].flipflop = qfalse;
				continue;
			}
			if (i == LEGS_STRAFE_LEFT) {
				animations[i] = animations[LEGS_RUN];
				animations[i].reversed = qfalse;
				animations[i].flipflop = qfalse;
				continue;
			}
			if (i == LEGS_STRAFE_RIGHT) {
				animations[i] = animations[LEGS_RUN];
				animations[i].reversed = qfalse;
				animations[i].flipflop = qfalse;
				continue;
			}
			break;
		}
		animations[i].firstFrame = atoi(token);
		// leg only frames are adjusted to not count the upper body only frames
		if (i == LEGS_WALKCR) {
			skip = animations[LEGS_WALKCR].firstFrame - animations[TORSO_GESTURE].firstFrame;
		}
		if (i >= LEGS_WALKCR && i < TORSO_GETFLAG) {
			animations[i].firstFrame -= skip;
		}

		token = COM_Parse(&text_p);
		if (!*token) {
			break;
		}
		animations[i].numFrames = atoi(token);

		animations[i].reversed = qfalse;
		animations[i].flipflop = qfalse;
		// if numFrames is negative the animation is reversed
		if (animations[i].numFrames < 0) {
			animations[i].numFrames = -animations[i].numFrames;
			animations[i].reversed = qtrue;
		}

		token = COM_Parse(&text_p);
		if (!*token) {
			break;
		}
		animations[i].loopFrames = atoi(token);

		token = COM_Parse(&text_p);
		if (!*token) {
			break;
		}
		fps = atof(token);
		if (fps == 0) {
			fps = 1;
		}
		animations[i].frameLerp = 1000 / fps;
		animations[i].initialLerp = 1000 / fps;
	}

	if (i != MAX_ANIMATIONS) {
		CG_Printf("Error parsing animation file: %s\n", filename);
		return qfalse;
	}

	// crouch backward animation
	memcpy(&animations[LEGS_BACKCR], &animations[LEGS_WALKCR], sizeof (animation_t));
	animations[LEGS_BACKCR].reversed = qtrue;
	// walk backward animation
	memcpy(&animations[LEGS_BACKWALK], &animations[LEGS_WALK], sizeof (animation_t));
	animations[LEGS_BACKWALK].reversed = qtrue;
	// flag moving fast
	animations[FLAG_RUN].firstFrame = 0;
	animations[FLAG_RUN].numFrames = 16;
	animations[FLAG_RUN].loopFrames = 16;
	animations[FLAG_RUN].frameLerp = 1000 / 15;
	animations[FLAG_RUN].initialLerp = 1000 / 15;
	animations[FLAG_RUN].reversed = qfalse;
	// flag not moving or moving slowly
	animations[FLAG_STAND].firstFrame = 16;
	animations[FLAG_STAND].numFrames = 5;
	animations[FLAG_STAND].loopFrames = 0;
	animations[FLAG_STAND].frameLerp = 1000 / 20;
	animations[FLAG_STAND].initialLerp = 1000 / 20;
	animations[FLAG_STAND].reversed = qfalse;
	// flag speeding up
	animations[FLAG_STAND2RUN].firstFrame = 16;
	animations[FLAG_STAND2RUN].numFrames = 5;
	animations[FLAG_STAND2RUN].loopFrames = 1;
	animations[FLAG_STAND2RUN].frameLerp = 1000 / 15;
	animations[FLAG_STAND2RUN].initialLerp = 1000 / 15;
	animations[FLAG_STAND2RUN].reversed = qtrue;
	//
	// new anims changes
	//
	animations[TORSO_GETFLAG].flipflop = qtrue;
	animations[TORSO_GUARDBASE].flipflop = qtrue;
	animations[TORSO_PATROL].flipflop = qtrue;
	animations[TORSO_AFFIRMATIVE].flipflop = qtrue;
	animations[TORSO_NEGATIVE].flipflop = qtrue;
	//
	return qtrue;
}

/*
======================
CG_ParseEyesFile

Read eyes definitions.  Maybe this should be done engine-side for mod compatiblity? :S
======================
 */
static qboolean CG_ParseEyesFile(const char *filename, clientInfo_t *ci) {
	char *text_p;
	int len;
	int i;
	char *token;
	char text[20000];
	fileHandle_t f;
	// load the file
	len = trap_FS_FOpenFile(filename, &f, FS_READ);
	if (len <= 0) {
		return qfalse;
	}
	if (len >= sizeof ( text) - 1) {
		CG_Printf("File %s too long\n", filename);
		trap_FS_FCloseFile(f);
		return qfalse;
	}
	trap_FS_Read(text, len, f);
	text[len] = 0;
	trap_FS_FCloseFile(f);

	// parse the text
	text_p = text;


	// read optional parameters
	while (1) {
		token = COM_Parse(&text_p);
		if (!token) {
			break;
		}


		if (!Q_stricmp(token, "eyes")) { // leilei - EYES
			for (i = 0; i < 3; i++) {
				token = COM_Parse(&text_p);
				if (!token) {
					break;
				}
				ci->eyepos[i] = atof(token);
			}
			continue;
		}
		break;
	}

	return qtrue;
}

/*
==========================
CG_FileExists
==========================
 */
static qboolean CG_FileExists(const char *filename) {
	int len;

	len = trap_FS_FOpenFile(filename, NULL, FS_READ);
	if (len > 0) {
		return qtrue;
	}
	return qfalse;
}

/*
==========================
CG_FindClientModelFile
==========================
 */
static qboolean CG_FindClientModelFile(char *filename, int length, clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *base, const char *ext) {
	char *team, *charactersFolder;
	int i;

	if (cgs.gametype >= GT_TEAM && cgs.ffa_gt != 1) {
		switch (ci->team) {
			case TEAM_BLUE:
			{
				team = "blue";
				break;
			}
			default:
			{
				team = "red";
				break;
			}
		}
	} else {
		team = "default";
	}
	charactersFolder = "";
	while (1) {
		for (i = 0; i < 2; i++) {
			if (i == 0 && teamName && *teamName) {
				//								"models/players/characters/sergei/stroggs/lower_lily_red.skin"
				Com_sprintf(filename, length, "models/players/%s%s/%s%s_%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, team, ext);
			} else {
				//								"models/players/characters/sergei/lower_lily_red.skin"
				Com_sprintf(filename, length, "models/players/%s%s/%s_%s_%s.%s", charactersFolder, modelName, base, skinName, team, ext);
			}
			if (CG_FileExists(filename)) {
				return qtrue;
			}
			if (cgs.gametype >= GT_TEAM && cgs.ffa_gt != 1) {
				if (i == 0 && teamName && *teamName) {
					//								"models/players/characters/sergei/stroggs/lower_red.skin"
					Com_sprintf(filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, team, ext);
				} else {
					//								"models/players/characters/sergei/lower_red.skin"
					Com_sprintf(filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, team, ext);
				}
			} else {
				if (i == 0 && teamName && *teamName) {
					//								"models/players/characters/sergei/stroggs/lower_lily.skin"
					Com_sprintf(filename, length, "models/players/%s%s/%s%s_%s.%s", charactersFolder, modelName, teamName, base, skinName, ext);
				} else {
					//								"models/players/characters/sergei/lower_lily.skin"
					Com_sprintf(filename, length, "models/players/%s%s/%s_%s.%s", charactersFolder, modelName, base, skinName, ext);
				}
			}
			if (CG_FileExists(filename)) {
				return qtrue;
			}
			if (!teamName || !*teamName) {
				break;
			}
		}
		// if tried the heads folder first
		if (charactersFolder[0]) {
			break;
		}
		charactersFolder = "characters/";
	}

	return qfalse;
}

/*
==========================
CG_FindClientHeadFile
==========================
 */
static qboolean CG_FindClientHeadFile(char *filename, int length, clientInfo_t *ci, const char *teamName, const char *headModelName, const char *headSkinName, const char *base, const char *ext) {
	char *team, *headsFolder;
	int i;

	if (cgs.gametype >= GT_TEAM && cgs.ffa_gt != 1) {
		switch (ci->team) {
			case TEAM_BLUE:
			{
				team = "blue";
				break;
			}
			default:
			{
				team = "red";
				break;
			}
		}
	} else {
		team = "default";
	}

	if (headModelName[0] == '*') {
		headsFolder = "heads/";
		headModelName++;
	} else {
		headsFolder = "";
	}
	while (1) {
		for (i = 0; i < 2; i++) {
			if (i == 0 && teamName && *teamName) {
				Com_sprintf(filename, length, "models/players/%s%s/%s/%s%s_%s.%s", headsFolder, headModelName, headSkinName, teamName, base, team, ext);
			} else {
				Com_sprintf(filename, length, "models/players/%s%s/%s/%s_%s.%s", headsFolder, headModelName, headSkinName, base, team, ext);
			}
			if (CG_FileExists(filename)) {
				return qtrue;
			}
			if (cgs.gametype >= GT_TEAM && cgs.ffa_gt != 1) {
				if (i == 0 && teamName && *teamName) {
					Com_sprintf(filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, team, ext);
				} else {
					Com_sprintf(filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, team, ext);
				}
			} else {
				if (i == 0 && teamName && *teamName) {
					Com_sprintf(filename, length, "models/players/%s%s/%s%s_%s.%s", headsFolder, headModelName, teamName, base, headSkinName, ext);
				} else {
					Com_sprintf(filename, length, "models/players/%s%s/%s_%s.%s", headsFolder, headModelName, base, headSkinName, ext);
				}
			}
			if (CG_FileExists(filename)) {
				return qtrue;
			}
			if (!teamName || !*teamName) {
				break;
			}
		}
		// if tried the heads folder first
		if (headsFolder[0]) {
			break;
		}
		headsFolder = "heads/";
	}

	return qfalse;
}

/*
==========================
CG_RegisterClientSkin
==========================
 */
static qboolean CG_RegisterClientSkin(clientInfo_t *ci, const char *teamName, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName) {
	char filename[MAX_QPATH];

	if (ci->onepiece) {
		Com_sprintf(filename, sizeof ( filename), "models/playerfs/%s/%stris_%s.skin", modelName, teamName, skinName);
		ci->legsSkin = trap_R_RegisterSkin(filename);
		if (!ci->legsSkin) {
			Com_Printf("Onepieced model skin load failure: %s\n", filename);
		}
	} else {
		if (CG_FindClientModelFile(filename, sizeof (filename), ci, teamName, modelName, skinName, "lower", "skin")) {
			ci->legsSkin = trap_R_RegisterSkin(filename);
		}
		if (!ci->legsSkin) {
			Com_Printf("Leg skin load failure: %s\n", filename);
		}
	}

	if (CG_FindClientModelFile(filename, sizeof (filename), ci, teamName, modelName, skinName, "upper", "skin")) {
		ci->torsoSkin = trap_R_RegisterSkin(filename);
	}
	if (!ci->torsoSkin) {
		Com_Printf("Torso skin load failure: %s\n", filename);
	}

	if (CG_FindClientHeadFile(filename, sizeof (filename), ci, teamName, headModelName, headSkinName, "head", "skin")) {
		ci->headSkin = trap_R_RegisterSkin(filename);
	}
	if (!ci->headSkin) {
		Com_Printf("Head skin load failure: %s\n", filename);
	}

	// if any skins failed to load
	if (!ci->legsSkin || !ci->torsoSkin || !ci->headSkin) {
		return qfalse;
	}
	return qtrue;
}

/*
==========================
CG_RegisterClientModelname
==========================
 */
static qboolean CG_RegisterClientModelname(clientInfo_t *ci, const char *modelName, const char *skinName, const char *headModelName, const char *headSkinName, const char *teamName) {
	char filename[MAX_QPATH * 2];
	const char *headName;
	char newTeamName[MAX_QPATH * 2];
	int FSloaded = 0;

	if (headModelName[0] == '\0') {
		headName = modelName;
	} else {
		headName = headModelName;
	}

	// leilei - onepiece model loading for certain maps or WIP animation debug
	ci->onepiece = 0;
	if (cg_enableFS.integer) {
		Com_sprintf(filename, sizeof ( filename), "models/playerfs/%s/tris.mdr", modelName);
		ci->legsModel = trap_R_RegisterModel(filename);
		if (ci->legsModel) {
			FSloaded = 1;
			ci->onepiece = 1;
		}
		if (!ci->legsModel)
			FSloaded = 0; // just end it

		if (FSloaded) {
			Com_Printf("%s is fanservice\n", filename);
		} else {
			Com_Printf("%s can't provide fanservice\n", filename);
		}
	}

	// load the usual model
	if (FSloaded == 0) {
		Com_sprintf(filename, sizeof ( filename), "models/players/%s/lower.mdr", modelName);
		ci->legsModel = trap_R_RegisterModel(filename);
		if (!ci->legsModel) {
			Com_sprintf(filename, sizeof ( filename), "models/players/characters/%s/lower.mdr", modelName);
			ci->legsModel = trap_R_RegisterModel(filename);
			if (!ci->legsModel) {
				Com_sprintf(filename, sizeof ( filename), "models/players/%s/lower.md3", modelName);
				ci->legsModel = trap_R_RegisterModel(filename);
				if (!ci->legsModel) {
					Com_sprintf(filename, sizeof ( filename), "models/players/characters/%s/lower.md3", modelName);
					ci->legsModel = trap_R_RegisterModel(filename);
					if (!ci->legsModel) {
						Com_Printf("Failed to load model file %s\n", filename);
						return qfalse;
					}
				}
			}
		}

		Com_sprintf(filename, sizeof ( filename), "models/players/%s/upper.mdr", modelName);
		ci->torsoModel = trap_R_RegisterModel(filename);
		if (!ci->torsoModel) {
			Com_sprintf(filename, sizeof ( filename), "models/players/characters/%s/upper.mdr", modelName);
			ci->torsoModel = trap_R_RegisterModel(filename);
			if (!ci->torsoModel) {
				Com_sprintf(filename, sizeof ( filename), "models/players/%s/upper.md3", modelName);
				ci->torsoModel = trap_R_RegisterModel(filename);
				if (!ci->torsoModel) {
					Com_sprintf(filename, sizeof ( filename), "models/players/characters/%s/upper.md3", modelName);
					ci->torsoModel = trap_R_RegisterModel(filename);
					if (!ci->torsoModel) {
						Com_Printf("Failed to load model file %s\n", filename);
						return qfalse;
					}
				}
			}
		}

	} // enableFS

	if (headName[0] == '*') {
		Com_sprintf(filename, sizeof ( filename), "models/players/heads/%s/%s.mdr", &headModelName[1], &headModelName[1]);
	} else {
		Com_sprintf(filename, sizeof ( filename), "models/players/%s/head.mdr", headName);
	}
	ci->headModel = trap_R_RegisterModel(filename);
	// if the head model could not be found and we didn't load from the heads folder try to load from there
	if (!ci->headModel && headName[0] != '*') {
		Com_sprintf(filename, sizeof ( filename), "models/players/heads/%s/%s.mdr", headModelName, headModelName);
		ci->headModel = trap_R_RegisterModel(filename);
	}
	if (!ci->headModel) {
		if (headName[0] == '*') {
			Com_sprintf(filename, sizeof ( filename), "models/players/heads/%s/%s.md3", &headModelName[1], &headModelName[1]);
		} else {
			Com_sprintf(filename, sizeof ( filename), "models/players/%s/head.md3", headName);
		}
		ci->headModel = trap_R_RegisterModel(filename);
		// if the head model could not be found and we didn't load from the heads folder try to load from there
		if (!ci->headModel && headName[0] != '*') {
			Com_sprintf(filename, sizeof ( filename), "models/players/heads/%s/%s.md3", headModelName, headModelName);
			ci->headModel = trap_R_RegisterModel(filename);
		}
		if (!ci->headModel) {
			Com_Printf("Failed to load model file %s\n", filename);
			return qfalse;
		}
	}

	// if any skins failed to load, return failure
	if (!CG_RegisterClientSkin(ci, teamName, modelName, skinName, headName, headSkinName)) {
		if (teamName && *teamName) {
			Com_Printf("Failed to load skin file: %s : %s : %s, %s : %s\n", teamName, modelName, skinName, headName, headSkinName);
			if (ci->team == TEAM_BLUE) {
				Com_sprintf(newTeamName, sizeof (newTeamName), "%s/", DEFAULT_BLUETEAM_NAME);
			} else {
				Com_sprintf(newTeamName, sizeof (newTeamName), "%s/", DEFAULT_REDTEAM_NAME);
			}
			if (!CG_RegisterClientSkin(ci, newTeamName, modelName, skinName, headName, headSkinName)) {
				Com_Printf("Failed to load skin file: %s : %s : %s, %s : %s\n", newTeamName, modelName, skinName, headName, headSkinName);
				return qfalse;
			}
		} else {
			Com_Printf("Failed to load skin file: %s : %s, %s : %s\n", modelName, skinName, headName, headSkinName);
			return qfalse;
		}
	}

	// load the animations
	// leilei - load one piece animation
	if (ci->onepiece) {
		Com_sprintf(filename, sizeof ( filename), "models/playerfs/%s/animation.cfg", modelName);
		if (!CG_ParseAnimationFile(filename, ci)) {
			Com_Printf("Failed to load animation file %s\n", filename);
		}
	} else {
		Com_sprintf(filename, sizeof ( filename), "models/players/%s/animation.cfg", modelName);
		if (!CG_ParseAnimationFile(filename, ci)) {
			Com_sprintf(filename, sizeof ( filename), "models/players/characters/%s/animation.cfg", modelName);
			if (!CG_ParseAnimationFile(filename, ci)) {
				Com_Printf("Failed to load animation file %s\n", filename);
				return qfalse;
			}
		}
	}




	// leilei - load eyes
	Com_sprintf(filename, sizeof ( filename), "models/players/%s/eyes.cfg", modelName);
	if (!CG_ParseEyesFile(filename, ci)) {
		//	Com_Printf( "No eyes for %s\n", filename );
	}



	if (CG_FindClientHeadFile(filename, sizeof (filename), ci, teamName, headName, headSkinName, "icon", "skin")) {
		ci->modelIcon = trap_R_RegisterShaderNoMip(filename);
	} else if (CG_FindClientHeadFile(filename, sizeof (filename), ci, teamName, headName, headSkinName, "icon", "tga")) {
		ci->modelIcon = trap_R_RegisterShaderNoMip(filename);
	}

	if (!ci->modelIcon) {
		return qfalse;
	}

	return qtrue;
}

/*
====================
CG_ColorFromString
====================
 */
static void CG_ColorFromString(const char *v, vec3_t color) {
	int val;

	VectorClear(color);

	val = atoi(v);

	if (val < 1 || val > 7) {
		VectorSet(color, 1, 1, 1);
		return;
	}

	if (val & 1) {
		color[2] = 1.0f;
	}
	if (val & 2) {
		color[1] = 1.0f;
	}
	if (val & 4) {
		color[0] = 1.0f;
	}
}

/*
===================
CG_LoadClientInfo

Load it now, taking the disk hits.
This will usually be deferred to a safe time
===================
 */
static void CG_LoadClientInfo(int clientNum, clientInfo_t *ci) {
	const char *dir, *fallback;
	int i, modelloaded;
	const char *s;
	char teamname[MAX_QPATH];

	teamname[0] = 0;
#ifdef MISSIONPACK
	if (cgs.gametype >= GT_TEAM && cgs.ffa_gt != 1) {
		if (ci->team == TEAM_BLUE) {
			Q_strncpyz(teamname, cg_blueTeamName.string, sizeof (teamname));
		} else {
			Q_strncpyz(teamname, cg_redTeamName.string, sizeof (teamname));
		}
	}
	if (teamname[0]) {
		strcat(teamname, "/");
	}
#endif
	modelloaded = qtrue;
	if (!CG_RegisterClientModelname(ci, ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname)) {
		if (cg_buildScript.integer) {
			CG_Error("CG_RegisterClientModelname( %s, %s, %s, %s %s ) failed", ci->modelName, ci->skinName, ci->headModelName, ci->headSkinName, teamname);
		}

		// fall back to default team name
		if (cgs.gametype >= GT_TEAM && cgs.ffa_gt != 1) {
			// keep skin name
			if (ci->team == TEAM_BLUE) {
				Q_strncpyz(teamname, DEFAULT_BLUETEAM_NAME, sizeof (teamname));
			} else {
				Q_strncpyz(teamname, DEFAULT_REDTEAM_NAME, sizeof (teamname));
			}
			if (!CG_RegisterClientModelname(ci, DEFAULT_TEAM_MODEL, ci->skinName, DEFAULT_TEAM_HEAD, ci->skinName, teamname)) {
				CG_Error("DEFAULT_TEAM_MODEL / skin (%s/%s) failed to register", DEFAULT_TEAM_MODEL, ci->skinName);
			}
		} else {
			if (!CG_RegisterClientModelname(ci, DEFAULT_MODEL, "default", DEFAULT_MODEL, "default", teamname)) {
				CG_Error("DEFAULT_MODEL (%s) failed to register", DEFAULT_MODEL);
			}
		}
		modelloaded = qfalse;
	}

	ci->newAnims = qfalse;
	if (ci->torsoModel) {
		orientation_t tag;
		// if the torso model has the "tag_flag"
		if (trap_R_LerpTag(&tag, ci->torsoModel, 0, 0, 1, "tag_flag")) {
			ci->newAnims = qtrue;
		}
	}

	// sounds
	dir = ci->modelName;
	fallback = (cgs.gametype >= GT_TEAM && cgs.ffa_gt != 1) ? DEFAULT_TEAM_MODEL : DEFAULT_MODEL;

	for (i = 0; i < MAX_CUSTOM_SOUNDS; i++) {
		s = cg_customSoundNames[i];
		if (!s) {
			break;
		}
		ci->sounds[i] = 0;
		// if the model didn't load use the sounds of the default model
		if (modelloaded) {
			ci->sounds[i] = trap_S_RegisterSound(va("sound/player/%s/%s", dir, s + 1), qfalse);
		}
		if (!ci->sounds[i]) {
			ci->sounds[i] = trap_S_RegisterSound(va("sound/player/%s/%s", fallback, s + 1), qfalse);
		}
	}

	ci->deferred = qfalse;

	// reset any existing players and bodies, because they might be in bad
	// frames for this new model
	for (i = 0; i < MAX_GENTITIES; i++) {
		if (cg_entities[i].currentState.clientNum == clientNum
				&& cg_entities[i].currentState.eType == ET_PLAYER) {
			CG_ResetPlayerEntity(&cg_entities[i]);
		}
	}
}

/*
======================
CG_CopyClientInfoModel
======================
 */
static void CG_CopyClientInfoModel(clientInfo_t *from, clientInfo_t *to) {
	VectorCopy(from->headOffset, to->headOffset);
	VectorCopy(from->eyepos, to->eyepos);
	to->footsteps = from->footsteps;
	to->gender = from->gender;

	to->legsModel = from->legsModel;
	to->legsSkin = from->legsSkin;
	to->torsoModel = from->torsoModel;
	to->torsoSkin = from->torsoSkin;
	to->headModel = from->headModel;
	to->headSkin = from->headSkin;
	to->modelIcon = from->modelIcon;

	to->newAnims = from->newAnims;

	memcpy(to->animations, from->animations, sizeof ( to->animations));
	memcpy(to->sounds, from->sounds, sizeof ( to->sounds));
}

/*
======================
CG_ScanForExistingClientInfo
======================
 */
static qboolean CG_ScanForExistingClientInfo(clientInfo_t *ci) {
	int i;
	clientInfo_t *match;

	for (i = 0; i < cgs.maxclients; i++) {
		match = &cgs.clientinfo[ i ];
		if (!match->infoValid) {
			continue;
		}
		if (match->deferred) {
			continue;
		}
		if (Q_strequal(ci->modelName, match->modelName)
				&& Q_strequal(ci->skinName, match->skinName)
				&& Q_strequal(ci->headModelName, match->headModelName)
				&& Q_strequal(ci->headSkinName, match->headSkinName)
				&& Q_strequal(ci->blueTeam, match->blueTeam)
				&& Q_strequal(ci->redTeam, match->redTeam)
				&& (cgs.gametype < GT_TEAM || cgs.ffa_gt == 1 || ci->team == match->team)) {
			// this clientinfo is identical, so use it's handles

			ci->deferred = qfalse;

			CG_CopyClientInfoModel(match, ci);

			return qtrue;
		}
	}

	// nothing matches, so defer the load
	return qfalse;
}

/*
======================
CG_SetDeferredClientInfo

We aren't going to load it now, so grab some other
client's info to use until we have some spare time.
======================
 */
static void CG_SetDeferredClientInfo(int clientNum, clientInfo_t *ci) {
	int i;
	clientInfo_t *match;

	// if someone else is already the same models and skins we
	// can just load the client info
	for (i = 0; i < cgs.maxclients; i++) {
		match = &cgs.clientinfo[ i ];
		if (!match->infoValid || match->deferred) {
			continue;
		}
		if (!Q_strequal(ci->skinName, match->skinName) ||
				!Q_strequal(ci->modelName, match->modelName) ||
				(cgs.gametype >= GT_TEAM && cgs.ffa_gt != 1 && ci->team != match->team)) {
			continue;
		}
		// just load the real info cause it uses the same models and skins
		CG_LoadClientInfo(clientNum, ci);
		return;
	}

	// if we are in teamplay, only grab a model if the skin is correct
	if (cgs.gametype >= GT_TEAM && cgs.ffa_gt != 1) {
		for (i = 0; i < cgs.maxclients; i++) {
			match = &cgs.clientinfo[ i ];
			if (!match->infoValid || match->deferred) {
				continue;
			}
			if (!Q_strequal(ci->skinName, match->skinName) ||
					(cgs.gametype >= GT_TEAM && cgs.ffa_gt != 1 && ci->team != match->team)) {
				continue;
			}
			ci->deferred = qtrue;
			CG_CopyClientInfoModel(match, ci);
			return;
		}
		// load the full model, because we don't ever want to show
		// an improper team skin.  This will cause a hitch for the first
		// player, when the second enters.  Combat shouldn't be going on
		// yet, so it shouldn't matter
		CG_LoadClientInfo(clientNum, ci);
		return;
	}

	// find the first valid clientinfo and grab its stuff
	for (i = 0; i < cgs.maxclients; i++) {
		match = &cgs.clientinfo[ i ];
		if (!match->infoValid) {
			continue;
		}

		ci->deferred = qtrue;
		CG_CopyClientInfoModel(match, ci);
		return;
	}

	// we should never get here...
	CG_Printf("CG_SetDeferredClientInfo: no valid clients!\n");

	CG_LoadClientInfo(clientNum, ci);
}

/*
======================
CG_NewClientInfo
======================
 */
void CG_NewClientInfo(int clientNum) {
	clientInfo_t *ci;
	clientInfo_t newInfo;
	const char *configstring;
	const char *v;
	char *slash;

	ci = &cgs.clientinfo[clientNum];

	configstring = CG_ConfigString(clientNum + CS_PLAYERS);
	if (!configstring[0]) {
		memset(ci, 0, sizeof ( *ci));
		return; // player just left
	}

	// build into a temp buffer so the defer checks can use
	// the old value
	memset(&newInfo, 0, sizeof ( newInfo));

	// isolate the player's name
	v = Info_ValueForKey(configstring, "n");
	Q_strncpyz(newInfo.name, v, sizeof ( newInfo.name));

	// colors
	v = Info_ValueForKey(configstring, "c1");
	CG_ColorFromString(v, newInfo.color1);

	v = Info_ValueForKey(configstring, "c2");
	CG_ColorFromString(v, newInfo.color2);

	// bot skill
	v = Info_ValueForKey(configstring, "skill");
	newInfo.botSkill = atoi(v);

	// handicap
	v = Info_ValueForKey(configstring, "hc");
	newInfo.handicap = atoi(v);

	// wins
	v = Info_ValueForKey(configstring, "w");
	newInfo.wins = atoi(v);

	// losses
	v = Info_ValueForKey(configstring, "l");
	newInfo.losses = atoi(v);

	// team
	v = Info_ValueForKey(configstring, "t");
	newInfo.team = atoi(v);

	// team task
	v = Info_ValueForKey(configstring, "tt");
	newInfo.teamTask = atoi(v);

	// team leader
	v = Info_ValueForKey(configstring, "tl");
	newInfo.teamLeader = atoi(v);

	v = Info_ValueForKey(configstring, "g_redteam");
	Q_strncpyz(newInfo.redTeam, v, MAX_TEAMNAME);

	v = Info_ValueForKey(configstring, "g_blueteam");
	Q_strncpyz(newInfo.blueTeam, v, MAX_TEAMNAME);

	// model
	v = Info_ValueForKey(configstring, "model");
	if (cg_forceModel.integer) {
		// forcemodel makes everyone use a single model
		// to prevent load hitches
		char modelStr[MAX_QPATH];
		char *skin;

		if (cgs.gametype >= GT_TEAM && cgs.ffa_gt != 1) {
			Q_strncpyz(newInfo.modelName, DEFAULT_TEAM_MODEL, sizeof ( newInfo.modelName));
			Q_strncpyz(newInfo.skinName, "default", sizeof ( newInfo.skinName));
		} else {
			trap_Cvar_VariableStringBuffer("model", modelStr, sizeof ( modelStr));
			if ((skin = strchr(modelStr, '/')) == NULL) {
				skin = "default";
			} else {
				*skin++ = 0;
			}

			Q_strncpyz(newInfo.skinName, skin, sizeof ( newInfo.skinName));
			Q_strncpyz(newInfo.modelName, modelStr, sizeof ( newInfo.modelName));
		}

		if (cgs.gametype >= GT_TEAM && cgs.ffa_gt != 1) {
			// keep skin name
			slash = strchr(v, '/');
			if (slash) {
				Q_strncpyz(newInfo.skinName, slash + 1, sizeof ( newInfo.skinName));
			}
		}
	} else {
		Q_strncpyz(newInfo.modelName, v, sizeof ( newInfo.modelName));

		slash = strchr(newInfo.modelName, '/');
		if (!slash) {
			// modelName didn not include a skin name
			Q_strncpyz(newInfo.skinName, "default", sizeof ( newInfo.skinName));
		} else {
			Q_strncpyz(newInfo.skinName, slash + 1, sizeof ( newInfo.skinName));
			// truncate modelName
			*slash = 0;
		}
	}

	// head model
	v = Info_ValueForKey(configstring, "hmodel");
	if (cg_forceModel.integer) {
		// forcemodel makes everyone use a single model
		// to prevent load hitches
		char modelStr[MAX_QPATH];
		char *skin;

		if (cgs.gametype >= GT_TEAM && cgs.ffa_gt != 1) {
			Q_strncpyz(newInfo.headModelName, DEFAULT_TEAM_MODEL, sizeof ( newInfo.headModelName));
			Q_strncpyz(newInfo.headSkinName, "default", sizeof ( newInfo.headSkinName));
		} else {
			trap_Cvar_VariableStringBuffer("headmodel", modelStr, sizeof ( modelStr));
			if ((skin = strchr(modelStr, '/')) == NULL) {
				skin = "default";
			} else {
				*skin++ = 0;
			}

			Q_strncpyz(newInfo.headSkinName, skin, sizeof ( newInfo.headSkinName));
			Q_strncpyz(newInfo.headModelName, modelStr, sizeof ( newInfo.headModelName));
		}

		if (cgs.gametype >= GT_TEAM && cgs.ffa_gt != 1) {
			// keep skin name
			slash = strchr(v, '/');
			if (slash) {
				Q_strncpyz(newInfo.headSkinName, slash + 1, sizeof ( newInfo.headSkinName));
			}
		}
	} else {
		Q_strncpyz(newInfo.headModelName, v, sizeof ( newInfo.headModelName));

		slash = strchr(newInfo.headModelName, '/');
		if (!slash) {
			// modelName didn not include a skin name
			Q_strncpyz(newInfo.headSkinName, "default", sizeof ( newInfo.headSkinName));
		} else {
			Q_strncpyz(newInfo.headSkinName, slash + 1, sizeof ( newInfo.headSkinName));
			// truncate modelName
			*slash = 0;
		}
	}

	// scan for an existing clientinfo that matches this modelname
	// so we can avoid loading checks if possible
	if (!CG_ScanForExistingClientInfo(&newInfo)) {
		qboolean forceDefer;

		forceDefer = trap_MemoryRemaining() < 4000000;

		// if we are defering loads, just have it pick the first valid
		if (forceDefer || (cg_deferPlayers.integer && !cg_buildScript.integer && !cg.loading)) {
			// keep whatever they had if it won't violate team skins
			CG_SetDeferredClientInfo(clientNum, &newInfo);
			// if we are low on memory, leave them with this model
			if (forceDefer) {
				CG_Printf("Memory is low. Using deferred model.\n");
				newInfo.deferred = qfalse;
			}
		} else {
			CG_LoadClientInfo(clientNum, &newInfo);
		}
	}

	// replace whatever was there with the new one
	newInfo.infoValid = qtrue;
	*ci = newInfo;
}

/*
======================
CG_LoadDeferredPlayers

Called each frame when a player is dead
and the scoreboard is up
so deferred players can be loaded
======================
 */
void CG_LoadDeferredPlayers(void) {
	int i;
	clientInfo_t *ci;

	// scan for a deferred player to load
	for (i = 0, ci = cgs.clientinfo; i < cgs.maxclients; i++, ci++) {
		if (ci->infoValid && ci->deferred) {
			// if we are low on memory, leave it deferred
			if (trap_MemoryRemaining() < 4000000) {
				CG_Printf("Memory is low. Using deferred model.\n");
				ci->deferred = qfalse;
				continue;
			}
			CG_LoadClientInfo(i, ci);
			//			break;
		}
	}
}

/*
=============================================================================

PLAYER ANIMATION

=============================================================================
 */

/*
===============
CG_SetLerpFrameAnimation

may include ANIM_TOGGLEBIT
===============
 */
static void CG_SetLerpFrameAnimation(clientInfo_t *ci, lerpFrame_t *lf, int newAnimation) {
	animation_t *anim;

	lf->animationNumber = newAnimation;
	newAnimation &= ~ANIM_TOGGLEBIT;

	if (newAnimation < 0 || newAnimation >= MAX_TOTALANIMATIONS) {
		CG_Error("Bad animation number: %i", newAnimation);
	}

	anim = &ci->animations[ newAnimation ];

	lf->animation = anim;
	lf->animationTime = lf->frameTime + anim->initialLerp;

	if (cg_debugAnim.integer) {
		CG_Printf("Anim: %i\n", newAnimation);
	}
}

/*
===============
CG_RunLerpFrame

Sets cg.snap, cg.oldFrame, and cg.backlerp
cg.time should be between oldFrameTime and frameTime after exit
===============
 */
static void CG_RunLerpFrame(clientInfo_t *ci, lerpFrame_t *lf, int newAnimation, float speedScale) {
	int f, numFrames;
	animation_t *anim;

	// debugging tool to get no animations
	if (cg_animSpeed.integer == 0) {
		lf->oldFrame = lf->frame = lf->backlerp = 0;
		return;
	}

	// see if the animation sequence is switching
	if (newAnimation != lf->animationNumber || !lf->animation) {
		CG_SetLerpFrameAnimation(ci, lf, newAnimation);
	}

	// if we have passed the current frame, move it to
	// oldFrame and calculate a new frame
	if (cg.time >= lf->frameTime) {
		lf->oldFrame = lf->frame;
		lf->oldFrameTime = lf->frameTime;

		// get the next frame based on the animation
		anim = lf->animation;
		if (!anim->frameLerp) {
			return; // shouldn't happen
		}
		if (cg.time < lf->animationTime) {
			lf->frameTime = lf->animationTime; // initial lerp
		} else {
			lf->frameTime = lf->oldFrameTime + anim->frameLerp;
		}
		f = (lf->frameTime - lf->animationTime) / anim->frameLerp;
		f *= speedScale; // adjust for haste, etc

		numFrames = anim->numFrames;
		if (anim->flipflop) {
			numFrames *= 2;
		}
		if (f >= numFrames) {
			f -= numFrames;
			if (anim->loopFrames) {
				f %= anim->loopFrames;
				f += anim->numFrames - anim->loopFrames;
			} else {
				f = numFrames - 1;
				// the animation is stuck at the end, so it
				// can immediately transition to another sequence
				lf->frameTime = cg.time;
			}
		}
		if (anim->reversed) {
			lf->frame = anim->firstFrame + anim->numFrames - 1 - f;
		} else if (anim->flipflop && f >= anim->numFrames) {
			lf->frame = anim->firstFrame + anim->numFrames - 1 - (f % anim->numFrames);
		} else {
			lf->frame = anim->firstFrame + f;
		}
		if (cg.time > lf->frameTime) {
			lf->frameTime = cg.time;
			if (cg_debugAnim.integer) {
				CG_Printf("Clamp lf->frameTime\n");
			}
		}
	}

	if (lf->frameTime > cg.time + 200) {
		lf->frameTime = cg.time;
	}

	if (lf->oldFrameTime > cg.time) {
		lf->oldFrameTime = cg.time;
	}
	// calculate current lerp value
	if (lf->frameTime == lf->oldFrameTime) {
		lf->backlerp = 0;
	} else {
		lf->backlerp = 1.0 - (float) (cg.time - lf->oldFrameTime) / (lf->frameTime - lf->oldFrameTime);
	}
}

/*
===============
CG_ClearLerpFrame
===============
 */
static void CG_ClearLerpFrame(clientInfo_t *ci, lerpFrame_t *lf, int animationNumber) {
	lf->frameTime = lf->oldFrameTime = cg.time;
	CG_SetLerpFrameAnimation(ci, lf, animationNumber);
	lf->oldFrame = lf->frame = lf->animation->firstFrame;
}

/*
===============
CG_PlayerAnimation
===============
 */
static void CG_PlayerAnimation(centity_t *cent, int *legsOld, int *legs, float *legsBackLerp,
		int *torsoOld, int *torso, float *torsoBackLerp) {
	clientInfo_t *ci;
	int clientNum;
	float speedScale;

	clientNum = cent->currentState.clientNum;

	if (cg_noPlayerAnims.integer) {
		*legsOld = *legs = *torsoOld = *torso = 0;
		return;
	}

	if (cent->currentState.powerups & (1 << PW_HASTE)) {
		speedScale = 1.5;
	} else {
		speedScale = 1;
	}

	ci = &cgs.clientinfo[ clientNum ];

	// do the shuffle turn frames locally
	if (cent->pe.legs.yawing && (cent->currentState.legsAnim & ~ANIM_TOGGLEBIT) == LEGS_IDLE) {
		CG_RunLerpFrame(ci, &cent->pe.legs, LEGS_TURN, speedScale);
	} else {
		CG_RunLerpFrame(ci, &cent->pe.legs, cent->currentState.legsAnim, speedScale);
	}

	*legsOld = cent->pe.legs.oldFrame;
	*legs = cent->pe.legs.frame;
	*legsBackLerp = cent->pe.legs.backlerp;

	CG_RunLerpFrame(ci, &cent->pe.torso, cent->currentState.torsoAnim, speedScale);

	*torsoOld = cent->pe.torso.oldFrame;
	*torso = cent->pe.torso.frame;
	*torsoBackLerp = cent->pe.torso.backlerp;
}

/*
=============================================================================

PLAYER ANGLES

=============================================================================
 */

/*
==================
CG_SwingAngles
==================
 */
static void CG_SwingAngles(float destination, float swingTolerance, float clampTolerance,
		float speed, float *angle, qboolean *swinging) {
	float swing;
	float move;
	float scale;

	if (!*swinging) {
		// see if a swing should be started
		swing = AngleSubtract(*angle, destination);
		if (swing > swingTolerance || swing < -swingTolerance) {
			*swinging = qtrue;
		}
	}

	if (!*swinging) {
		return;
	}

	// modify the speed depending on the delta
	// so it doesn't seem so linear
	swing = AngleSubtract(destination, *angle);
	scale = fabs(swing);
	if (scale < swingTolerance * 0.5) {
		scale = 0.5;
	} else if (scale < swingTolerance) {
		scale = 1.0;
	} else {
		scale = 2.0;
	}

	// swing towards the destination angle
	if (swing >= 0) {
		move = cg.frametime * scale * speed;
		if (move >= swing) {
			move = swing;
			*swinging = qfalse;
		}
		*angle = AngleMod(*angle + move);
	} else {
		move = cg.frametime * scale * -speed;
		if (move <= swing) {
			move = swing;
			*swinging = qfalse;
		}
		*angle = AngleMod(*angle + move);
	}

	// clamp to no more than tolerance
	swing = AngleSubtract(destination, *angle);
	if (swing > clampTolerance) {
		*angle = AngleMod(destination - (clampTolerance - 1));
	} else if (swing < -clampTolerance) {
		*angle = AngleMod(destination + (clampTolerance - 1));
	}
}

/*
=================
CG_AddPainTwitch
=================
 */
static void CG_AddPainTwitch(centity_t *cent, vec3_t torsoAngles) {
	int t;
	float f;

	t = cg.time - cent->pe.painTime;
	if (t >= PAIN_TWITCH_TIME) {
		return;
	}

	f = 1.0 - (float) t / PAIN_TWITCH_TIME;

	if (cent->pe.painDirection) {
		torsoAngles[ROLL] += 20 * f;
	} else {
		torsoAngles[ROLL] -= 20 * f;
	}
}


/*
===============
CG_PlayerAngles

Handles seperate torso motion

  legs pivot based on direction of movement

  head always looks exactly at cent->lerpAngles

  if motion < 20 degrees, show in head only
  if < 45 degrees, also show in torso
===============
 */

vec3_t eyeat;

static void CG_PlayerAngles(centity_t *cent, vec3_t legs[3], vec3_t torso[3], vec3_t head[3]) {
	vec3_t legsAngles, torsoAngles, headAngles;
	float dest;
	static int movementOffsets[8] = {0, 22, 45, -22, 0, 22, -45, -22};
	vec3_t velocity;
	float speed;
	int dir, clientNum;
	clientInfo_t *ci = NULL;
	int camereyes;



	{
		VectorCopy(cent->lerpAngles, headAngles);
		headAngles[YAW] = AngleMod(headAngles[YAW]);
	}


	//headAngles[YAW] = AngleMod( headAngles[YAW] );


	VectorClear(legsAngles);
	VectorClear(torsoAngles);


	camereyes = 0;
	if (cent->currentState.number == cg.snap->ps.clientNum) {
		camereyes = 1; // it's me!
	}

	// leilei -- new third person camera prep
	cent->newcamrunning = 0;
	if (cg_cameramode.integer == 1) {
		if ((cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT) != TORSO_ATTACK) {
			cent->newcamrunning = 1;
		} else {
			cent->newcamrunning = 0;
		}
	}

	// --------- yaw -------------


	// turn head if reached the eye tolerance limit

	// allow yaw to drift a bit
	if ((cent->currentState.legsAnim & ~ANIM_TOGGLEBIT) != LEGS_IDLE
			|| ((cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT) != TORSO_STAND
			&& (cent->currentState.torsoAnim & ~ANIM_TOGGLEBIT) != TORSO_STAND2)) {
		// if not standing still, always point all in the same direction
		cent->pe.torso.yawing = qtrue; // always center
		cent->pe.torso.pitching = qtrue; // always center
		cent->pe.legs.yawing = qtrue; // always center

	}




	// etc

	if (cent->newcamrunning) {
		// lean towards the direction of travel
		VectorCopy(cent->currentState.pos.trDelta, velocity);
		speed = VectorNormalize(velocity);
		if (speed) {
			vec3_t veel;
			vec3_t fwad, rait;
			AngleVectors(veel, velocity, fwad, rait);
			speed *= 0.05f;
		}
	}

	// adjust legs for movement dir
	if (cent->currentState.eFlags & EF_DEAD) {
		// don't let dead bodies twitch
		dir = 0;
	} else {
		dir = cent->currentState.angles2[YAW];
		if (dir < 0 || dir > 7) {
			CG_Error("Bad player movement angle");
		}
	}


	legsAngles[YAW] = headAngles[YAW] + movementOffsets[ dir ];
	torsoAngles[YAW] = headAngles[YAW] + 0.25 * movementOffsets[ dir ];


	// torso
	if (cg_cameramode.integer == 1) {
		CG_SwingAngles(torsoAngles[YAW], 180, 90, cg_swingSpeed.value, &cent->pe.torso.yawAngle, &cent->pe.torso.yawing);
		CG_SwingAngles(legsAngles[YAW], 180, 90, cg_swingSpeed.value, &cent->pe.legs.yawAngle, &cent->pe.legs.yawing);
	} else {
		CG_SwingAngles(torsoAngles[YAW], 25, 90, cg_swingSpeed.value, &cent->pe.torso.yawAngle, &cent->pe.torso.yawing);
		CG_SwingAngles(legsAngles[YAW], 40, 90, cg_swingSpeed.value, &cent->pe.legs.yawAngle, &cent->pe.legs.yawing);
	}



	torsoAngles[YAW] = cent->pe.torso.yawAngle;
	legsAngles[YAW] = cent->pe.legs.yawAngle;


	// --------- pitch -------------
	// turn head if reached the eye tolerance limit

	// only show a fraction of the pitch angle in the torso

	{
		if (headAngles[PITCH] > 180) {
			dest = (-360 + headAngles[PITCH]) * 0.75f;
		} else {
			dest = headAngles[PITCH] * 0.75f;
		}
	}
	if (cg_cameramode.integer == 1) {
		CG_SwingAngles(dest, 15, 30, 0.1f, &cent->pe.torso.pitchAngle, &cent->pe.torso.pitching);

		headAngles[PITCH] = cent->pe.torso.pitchAngle;
		//headAngles[YAW] = cent->pe.torso.yawAngle;

		headAngles[YAW] = torsoAngles[YAW] + 0.25 * movementOffsets[ dir ];
		torsoAngles[PITCH] = cent->pe.torso.pitchAngle;
	} else {
		CG_SwingAngles(dest, 15, 30, 0.1f, &cent->pe.torso.pitchAngle, &cent->pe.torso.pitching);

		torsoAngles[PITCH] = cent->pe.torso.pitchAngle;
	}

	//
	clientNum = cent->currentState.clientNum;
	if (clientNum >= 0 && clientNum < MAX_CLIENTS) {
		ci = &cgs.clientinfo[ clientNum ];
		if (ci->fixedtorso) {
			torsoAngles[PITCH] = 0.0f;
		}
	//	if (camereyes) {
	//		torsoAngles[PITCH] = 0.0f; // don't pitch 
	//	}
	}



	// --------- roll -------------


	// lean towards the direction of travel
	VectorCopy(cent->currentState.pos.trDelta, velocity);
	speed = VectorNormalize(velocity);
	if (speed) {
		vec3_t axis[3];
		float side;

		speed *= 0.05f;

		AnglesToAxis(legsAngles, axis);
		side = speed * DotProduct(velocity, axis[1]);
		legsAngles[ROLL] -= side;

		side = speed * DotProduct(velocity, axis[0]);
		legsAngles[PITCH] += side;
	}

	//
	clientNum = cent->currentState.clientNum;
	if (clientNum >= 0 && clientNum < MAX_CLIENTS) {
		ci = &cgs.clientinfo[ clientNum ];


		if (ci->fixedlegs) {
			legsAngles[YAW] = torsoAngles[YAW];
			legsAngles[PITCH] = 0.0f;
			legsAngles[ROLL] = 0.0f;
		}

		// leilei - don't lean for our new strafe animations
		if ((cent->currentState.legsAnim & ~ANIM_TOGGLEBIT) == LEGS_STRAFE_LEFT || (cent->currentState.legsAnim & ~ANIM_TOGGLEBIT) == LEGS_STRAFE_RIGHT) {
			legsAngles[YAW] = torsoAngles[YAW];
			//legsAngles[PITCH] = 0.0f;
			legsAngles[ROLL] = 0.0f;
		}

	}

	// pain twitch
	CG_AddPainTwitch(cent, torsoAngles);
	// TODO: make eyes erode on pain twitch

	// leilei - eyes hack

	if (camereyes) {
		cent->eyesAngles[YAW] = headAngles[YAW];
		cent->eyesAngles[PITCH] = headAngles[PITCH];
		cent->eyesAngles[ROLL] = headAngles[ROLL];
	}

	// pull the angles back out of the hierarchial chain
	AnglesSubtract(headAngles, torsoAngles, headAngles);
	AnglesSubtract(torsoAngles, legsAngles, torsoAngles);
	AnglesToAxis(legsAngles, legs);
	AnglesToAxis(torsoAngles, torso);
	AnglesToAxis(headAngles, head);

	// eyes crap
	{
		vec3_t eyelookfrom;

		// offset from the model we have.
		VectorClear(eyelookfrom);
		if (ci) {
			VectorCopy(ci->eyepos, cent->pe.eyepos);
		}
		//VectorCopy(eyelookfrom, cent->pe.eyepos);			// leilei - copy eye poistion
	}

}


//==========================================================================

/*
===============
CG_HasteTrail
===============
 */
static void CG_HasteTrail(centity_t *cent) {
	localEntity_t *smoke;
	vec3_t origin;
	int anim;

	if (cent->trailTime > cg.time) {
		return;
	}
	anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT;
	if (anim != LEGS_RUN && anim != LEGS_BACK) {
		return;
	}

	cent->trailTime += 100;
	if (cent->trailTime < cg.time) {
		cent->trailTime = cg.time;
	}

	VectorCopy(cent->lerpOrigin, origin);
	origin[2] -= 16;

	smoke = CG_SmokePuff(origin, vec3_origin,
			8,
			1, 1, 1, 1,
			500,
			cg.time,
			0,
			0,
			cgs.media.hastePuffShader);

	// use the optimized local entity add
	smoke->leType = LE_SCALE_FADE;
}

/*
===============
CG_BreathPuffs
===============
 */
static void CG_BreathPuffs(centity_t *cent, refEntity_t *head) {
	clientInfo_t *ci;
	vec3_t up, origin;
	int contents;

	ci = &cgs.clientinfo[ cent->currentState.number ];

	if (!cg_enableBreath.integer) {
		return;
	}
	if (cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson) {
		return;
	}
	if (cent->currentState.eFlags & EF_DEAD) {
		return;
	}
	contents = CG_PointContents(head->origin, 0);
	if (contents & (CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA)) {
		return;
	}
	if (ci->breathPuffTime > cg.time) {
		return;
	}

	VectorSet(up, 0, 0, 8);
	VectorMA(head->origin, 8, head->axis[0], origin);
	VectorMA(origin, -4, head->axis[2], origin);
	CG_SmokePuff(origin, up, 16, 1, 1, 1, 0.66f, 1500, cg.time, cg.time + 400, LEF_PUFF_DONT_SCALE, cgs.media.shotgunSmokePuffShader);
	ci->breathPuffTime = cg.time + 2000;
}

/*
===============
CG_DustTrail
===============
 */
static void CG_DustTrail(centity_t *cent) {
	int anim;
	vec3_t end, vel;
	trace_t tr;

	if (!cg_enableDust.integer)
		return;

	if (cent->dustTrailTime > cg.time) {
		return;
	}

	anim = cent->pe.legs.animationNumber & ~ANIM_TOGGLEBIT;
	if (anim != LEGS_LANDB && anim != LEGS_LAND) {
		return;
	}

	cent->dustTrailTime += 40;
	if (cent->dustTrailTime < cg.time) {
		cent->dustTrailTime = cg.time;
	}

	VectorCopy(cent->currentState.pos.trBase, end);
	end[2] -= 64;
	CG_Trace(&tr, cent->currentState.pos.trBase, NULL, NULL, end, cent->currentState.number, MASK_PLAYERSOLID);

	if (!(tr.surfaceFlags & SURF_DUST))
		return;

	VectorCopy(cent->currentState.pos.trBase, end);
	end[2] -= 16;

	VectorSet(vel, 0, 0, -30);
	CG_SmokePuff(end, vel,
			24,
			.8f, .8f, 0.7f, 0.33f,
			500,
			cg.time,
			0,
			0,
			cgs.media.dustPuffShader);
}

/*
===============
CG_TrailItem
===============
 */
static void CG_TrailItem(centity_t *cent, qhandle_t hModel) {
	refEntity_t ent;
	vec3_t angles;
	vec3_t axis[3];

	VectorCopy(cent->lerpAngles, angles);
	angles[PITCH] = 0;
	angles[ROLL] = 0;
	AnglesToAxis(angles, axis);

	memset(&ent, 0, sizeof ( ent));
	VectorMA(cent->lerpOrigin, -16, axis[0], ent.origin);
	ent.origin[2] += 16;
	angles[YAW] += 90;
	AnglesToAxis(angles, ent.axis);

	ent.hModel = hModel;
	trap_R_AddRefEntityToScene(&ent);
}

/*
===============
CG_PlayerFlag
===============
 */
static void CG_PlayerFlag(centity_t *cent, qhandle_t hSkin, refEntity_t *torso) {
	clientInfo_t *ci;
	refEntity_t pole;
	refEntity_t flag;
	vec3_t angles, dir;
	int legsAnim, flagAnim, updateangles;
	float angle, d;

	// show the flag pole model
	memset(&pole, 0, sizeof (pole));
	pole.hModel = cgs.media.flagPoleModel;
	VectorCopy(torso->lightingOrigin, pole.lightingOrigin);
	pole.shadowPlane = torso->shadowPlane;
	pole.renderfx = torso->renderfx;
	CG_PositionEntityOnTag(&pole, torso, torso->hModel, "tag_flag");
	trap_R_AddRefEntityToScene(&pole);

	// show the flag model
	memset(&flag, 0, sizeof (flag));
	flag.hModel = cgs.media.flagFlapModel;
	flag.customSkin = hSkin;
	VectorCopy(torso->lightingOrigin, flag.lightingOrigin);
	flag.shadowPlane = torso->shadowPlane;
	flag.renderfx = torso->renderfx;

	VectorClear(angles);

	updateangles = qfalse;
	legsAnim = cent->currentState.legsAnim & ~ANIM_TOGGLEBIT;
	if (legsAnim == LEGS_IDLE || legsAnim == LEGS_IDLECR) {
		flagAnim = FLAG_STAND;
	} else if (legsAnim == LEGS_WALK || legsAnim == LEGS_WALKCR) {
		flagAnim = FLAG_STAND;
		updateangles = qtrue;
	} else {
		flagAnim = FLAG_RUN;
		updateangles = qtrue;
	}

	if (updateangles) {

		VectorCopy(cent->currentState.pos.trDelta, dir);
		// add gravity
		dir[2] += 100;
		VectorNormalize(dir);
		d = DotProduct(pole.axis[2], dir);
		// if there is anough movement orthogonal to the flag pole
		if (fabs(d) < 0.9) {
			//
			d = DotProduct(pole.axis[0], dir);
			if (d > 1.0f) {
				d = 1.0f;
			} else if (d < -1.0f) {
				d = -1.0f;
			}
			angle = acos(d);

			d = DotProduct(pole.axis[1], dir);
			if (d < 0) {
				angles[YAW] = 360 - angle * 180 / M_PI;
			} else {
				angles[YAW] = angle * 180 / M_PI;
			}
			if (angles[YAW] < 0)
				angles[YAW] += 360;
			if (angles[YAW] > 360)
				angles[YAW] -= 360;

			//vectoangles( cent->currentState.pos.trDelta, tmpangles );
			//angles[YAW] = tmpangles[YAW] + 45 - cent->pe.torso.yawAngle;
			// change the yaw angle
			CG_SwingAngles(angles[YAW], 25, 90, 0.15f, &cent->pe.flag.yawAngle, &cent->pe.flag.yawing);
		}
	}

	// set the yaw angle
	angles[YAW] = cent->pe.flag.yawAngle;
	// lerp the flag animation frames
	ci = &cgs.clientinfo[ cent->currentState.clientNum ];
	CG_RunLerpFrame(ci, &cent->pe.flag, flagAnim, 1);
	flag.oldframe = cent->pe.flag.oldFrame;
	flag.frame = cent->pe.flag.frame;
	flag.backlerp = cent->pe.flag.backlerp;

	AnglesToAxis(angles, flag.axis);
	CG_PositionRotatedEntityOnTag(&flag, &pole, pole.hModel, "tag_flag");

	trap_R_AddRefEntityToScene(&flag);
}

/*
===============
CG_PlayerTokens
===============
 */
static void CG_PlayerTokens(centity_t *cent, int renderfx) {
	int tokens, i, j;
	float angle;
	refEntity_t ent;
	vec3_t dir, origin;
	skulltrail_t *trail;
	if (cent->currentState.number >= MAX_CLIENTS) {
		return;
	}
	trail = &cg.skulltrails[cent->currentState.number];
	tokens = cent->currentState.generic1;
	if (!tokens) {
		trail->numpositions = 0;
		return;
	}

	if (tokens > MAX_SKULLTRAIL) {
		tokens = MAX_SKULLTRAIL;
	}

	// add skulls if there are more than last time
	for (i = 0; i < tokens - trail->numpositions; i++) {
		for (j = trail->numpositions; j > 0; j--) {
			VectorCopy(trail->positions[j - 1], trail->positions[j]);
		}
		VectorCopy(cent->lerpOrigin, trail->positions[0]);
	}
	trail->numpositions = tokens;

	// move all the skulls along the trail
	VectorCopy(cent->lerpOrigin, origin);
	for (i = 0; i < trail->numpositions; i++) {
		VectorSubtract(trail->positions[i], origin, dir);
		if (VectorNormalize(dir) > 30) {
			VectorMA(origin, 30, dir, trail->positions[i]);
		}
		VectorCopy(trail->positions[i], origin);
	}

	memset(&ent, 0, sizeof ( ent));
	if (cgs.clientinfo[ cent->currentState.clientNum ].team == TEAM_BLUE) {
		ent.hModel = cgs.media.redCubeModel;
	} else {
		ent.hModel = cgs.media.blueCubeModel;
	}
	ent.renderfx = renderfx;

	VectorCopy(cent->lerpOrigin, origin);
	for (i = 0; i < trail->numpositions; i++) {
		VectorSubtract(origin, trail->positions[i], ent.axis[0]);
		ent.axis[0][2] = 0;
		VectorNormalize(ent.axis[0]);
		VectorSet(ent.axis[2], 0, 0, 1);
		CrossProduct(ent.axis[0], ent.axis[2], ent.axis[1]);

		VectorCopy(trail->positions[i], ent.origin);
		angle = (((cg.time + 500 * MAX_SKULLTRAIL - 500 * i) / 16) & 255) * (M_PI * 2) / 255;
		ent.origin[2] += sin(angle) * 10;
		trap_R_AddRefEntityToScene(&ent);
		VectorCopy(trail->positions[i], origin);
	}
}

/*
===============
CG_PlayerPowerups
===============
 */
static void CG_PlayerPowerups(centity_t *cent, refEntity_t *torso) {
	int powerups;
	clientInfo_t *ci;

	powerups = cent->currentState.powerups;
	if (!powerups) {
		return;
	}

	// quad gives a dlight
	if (powerups & (1 << PW_QUAD)) {
		trap_R_AddLightToScene(cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1);
	}

	// flight plays a looped sound
	if (powerups & (1 << PW_FLIGHT)) {
		trap_S_AddLoopingSound(cent->currentState.number, cent->lerpOrigin, vec3_origin, cgs.media.flightSound);
	}

	ci = &cgs.clientinfo[ cent->currentState.clientNum ];
	// redflag
	if (powerups & (1 << PW_REDFLAG)) {
		if (ci->newAnims) {
			CG_PlayerFlag(cent, cgs.media.redFlagFlapSkin, torso);
		} else {
			CG_TrailItem(cent, cgs.media.redFlagModel);
		}
		trap_R_AddLightToScene(cent->lerpOrigin, 200 + (rand()&31), 1.0, 0.2f, 0.2f);
	}

	// blueflag
	if (powerups & (1 << PW_BLUEFLAG)) {
		if (ci->newAnims) {
			CG_PlayerFlag(cent, cgs.media.blueFlagFlapSkin, torso);
		} else {
			CG_TrailItem(cent, cgs.media.blueFlagModel);
		}
		trap_R_AddLightToScene(cent->lerpOrigin, 200 + (rand()&31), 0.2f, 0.2f, 1.0);
	}

	// neutralflag
	if (powerups & (1 << PW_NEUTRALFLAG)) {
		if (ci->newAnims) {
			CG_PlayerFlag(cent, cgs.media.neutralFlagFlapSkin, torso);
		} else {
			CG_TrailItem(cent, cgs.media.neutralFlagModel);
		}
		trap_R_AddLightToScene(cent->lerpOrigin, 200 + (rand()&31), 1.0, 1.0, 1.0);
	}

	// haste leaves smoke trails
	if (powerups & (1 << PW_HASTE)) {
		CG_HasteTrail(cent);
	}
}

/*
===============
CG_PlayerFloatSprite

Float a sprite over the player's head
===============
 */
static void CG_PlayerFloatSprite(centity_t *cent, qhandle_t shader) {
	int rf;
	refEntity_t ent;

	if (cent->currentState.number == cg.snap->ps.clientNum && !cg.renderingThirdPerson) {
		rf = RF_THIRD_PERSON; // only show in mirrors
	} else {
		rf = 0;
	}

	memset(&ent, 0, sizeof ( ent));
	VectorCopy(cent->lerpOrigin, ent.origin);
	ent.origin[2] += 48;
	ent.reType = RT_SPRITE;
	ent.customShader = shader;
	ent.radius = 10;
	ent.renderfx = rf;
	ent.shaderRGBA[0] = 255;
	ent.shaderRGBA[1] = 255;
	ent.shaderRGBA[2] = 255;
	ent.shaderRGBA[3] = 255;
	trap_R_AddRefEntityToScene(&ent);
}

/*
===============
CG_PlayerSprites

Float sprites over the player's head
===============
 */
static void CG_PlayerSprites(centity_t *cent) {
	int team;

	if (cent->currentState.eFlags & EF_CONNECTION) {
		CG_PlayerFloatSprite(cent, cgs.media.connectionShader);
		return;
	}

	if (cent->currentState.eFlags & EF_TALK) {
		CG_PlayerFloatSprite(cent, cgs.media.balloonShader);
		return;
	}

	if (cent->currentState.eFlags & EF_AWARD_IMPRESSIVE) {
		CG_PlayerFloatSprite(cent, cgs.media.medalImpressive);
		return;
	}

	if (cent->currentState.eFlags & EF_AWARD_EXCELLENT) {
		CG_PlayerFloatSprite(cent, cgs.media.medalExcellent);
		return;
	}

	if (cent->currentState.eFlags & EF_AWARD_GAUNTLET) {
		CG_PlayerFloatSprite(cent, cgs.media.medalGauntlet);
		return;
	}

	if (cent->currentState.eFlags & EF_AWARD_DEFEND) {
		CG_PlayerFloatSprite(cent, cgs.media.medalDefend);
		return;
	}

	if (cent->currentState.eFlags & EF_AWARD_ASSIST) {
		CG_PlayerFloatSprite(cent, cgs.media.medalAssist);
		return;
	}

	if (cent->currentState.eFlags & EF_AWARD_CAP) {
		CG_PlayerFloatSprite(cent, cgs.media.medalCapture);
		return;
	}

	team = cgs.clientinfo[ cent->currentState.clientNum ].team;
	if (!(cent->currentState.eFlags & EF_DEAD) &&
			cg.snap->ps.persistant[PERS_TEAM] == team &&
			cgs.gametype >= GT_TEAM && cgs.ffa_gt != 1) {
		if (cg_drawFriend.integer) {
			CG_PlayerFloatSprite(cent, cgs.media.friendShader);
		}
		return;
	}
}

/*
===============
CG_PlayerShadow

Returns the Z component of the surface being shadowed

  should it return a full plane instead of a Z?
===============
 */
#define SHADOW_DISTANCE  128

static qboolean CG_PlayerShadow(centity_t *cent, float *shadowPlane) {
	vec3_t end, mins = {-15, -15, 0}, maxs = {15, 15, 2};
	trace_t trace;
	float alpha;

	*shadowPlane = 0;

	if (cg_shadows.integer == 0) {
		return qfalse;
	}

	// no shadows when invisible
	if (cent->currentState.powerups & (1 << PW_INVIS)) {
		return qfalse;
	}

	// send a trace down from the player to the ground
	VectorCopy(cent->lerpOrigin, end);
	end[2] -= SHADOW_DISTANCE;

	trap_CM_BoxTrace(&trace, cent->lerpOrigin, end, mins, maxs, 0, MASK_PLAYERSOLID);

	// no shadow if too high
	if (trace.fraction == 1.0 || trace.startsolid || trace.allsolid) {
		return qfalse;
	}

	*shadowPlane = trace.endpos[2] + 1;

	if (cg_shadows.integer != 1) { // no mark for stencil or projection shadows
		return qtrue;
	}

	// fade the shadow out with height
	alpha = 1.0 - trace.fraction;

	// bk0101022 - hack / FPE - bogus planes?
	//assert( DotProduct( trace.plane.normal, trace.plane.normal ) != 0.0f ) 

	// add the mark as a temporary, so it goes directly to the renderer
	// without taking a spot in the cg_marks array
	CG_ImpactMark(cgs.media.shadowMarkShader, trace.endpos, trace.plane.normal,
			cent->pe.legs.yawAngle, alpha, alpha, alpha, 1, qfalse, 24, qtrue);

	return qtrue;
}

/*
===============
CG_PlayerSplash

Draw a mark at the water surface
===============
 */
static void CG_PlayerSplash(centity_t *cent) {
	vec3_t start, end;
	trace_t trace;
	int contents;
	polyVert_t verts[4];

	if (!cg_shadows.integer) {
		return;
	}

	VectorCopy(cent->lerpOrigin, end);
	end[2] -= 24;

	// if the feet aren't in liquid, don't make a mark
	// this won't handle moving water brushes, but they wouldn't draw right anyway...
	contents = CG_PointContents(end, 0);
	if (!(contents & (CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA))) {
		return;
	}

	VectorCopy(cent->lerpOrigin, start);
	start[2] += 32;

	// if the head isn't out of liquid, don't make a mark
	contents = CG_PointContents(start, 0);
	if (contents & (CONTENTS_SOLID | CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA)) {
		return;
	}

	// trace down to find the surface
	trap_CM_BoxTrace(&trace, start, end, NULL, NULL, 0, (CONTENTS_WATER | CONTENTS_SLIME | CONTENTS_LAVA));

	if (trace.fraction == 1.0) {
		return;
	}

	// create a mark polygon
	VectorCopy(trace.endpos, verts[0].xyz);
	verts[0].xyz[0] -= 32;
	verts[0].xyz[1] -= 32;
	verts[0].st[0] = 0;
	verts[0].st[1] = 0;
	verts[0].modulate[0] = 255;
	verts[0].modulate[1] = 255;
	verts[0].modulate[2] = 255;
	verts[0].modulate[3] = 255;

	VectorCopy(trace.endpos, verts[1].xyz);
	verts[1].xyz[0] -= 32;
	verts[1].xyz[1] += 32;
	verts[1].st[0] = 0;
	verts[1].st[1] = 1;
	verts[1].modulate[0] = 255;
	verts[1].modulate[1] = 255;
	verts[1].modulate[2] = 255;
	verts[1].modulate[3] = 255;

	VectorCopy(trace.endpos, verts[2].xyz);
	verts[2].xyz[0] += 32;
	verts[2].xyz[1] += 32;
	verts[2].st[0] = 1;
	verts[2].st[1] = 1;
	verts[2].modulate[0] = 255;
	verts[2].modulate[1] = 255;
	verts[2].modulate[2] = 255;
	verts[2].modulate[3] = 255;

	VectorCopy(trace.endpos, verts[3].xyz);
	verts[3].xyz[0] += 32;
	verts[3].xyz[1] -= 32;
	verts[3].st[0] = 1;
	verts[3].st[1] = 0;
	verts[3].modulate[0] = 255;
	verts[3].modulate[1] = 255;
	verts[3].modulate[2] = 255;
	verts[3].modulate[3] = 255;

	trap_R_AddPolyToScene(cgs.media.wakeMarkShader, 4, verts);
}

/*
===============
CG_AddRefEntityWithPowerups

Adds a piece with modifications or duplications for powerups
Also called by CG_Missile for quad rockets, but nobody can tell...
===============
 */
void CG_AddRefEntityWithPowerups(refEntity_t *ent, entityState_t *state, int team, qboolean isMissile) {

	if (state->powerups & (1 << PW_INVIS)) {
		if ((cgs.dmflags & DF_INVIS) == 0) {
			ent->customShader = cgs.media.invisShader;
			trap_R_AddRefEntityToScene(ent);
		}
	} else {
		trap_R_AddRefEntityToScene(ent);
		if (!isMissile && (cgs.dmflags & DF_PLAYER_OVERLAY) && !(state->eFlags & EF_DEAD)) {
			switch (team) {
				case TEAM_RED:
					ent->customShader = cgs.media.redOverlay;
					trap_R_AddRefEntityToScene(ent);
					break;
				case TEAM_BLUE:
					ent->customShader = cgs.media.blueOverlay;
					trap_R_AddRefEntityToScene(ent);
					break;
				default:
					ent->customShader = cgs.media.neutralOverlay;
					trap_R_AddRefEntityToScene(ent);
			}
		}

		if (state->powerups & (1 << PW_QUAD)) {
			if (team == TEAM_RED)
				ent->customShader = cgs.media.redQuadShader;
			else
				ent->customShader = cgs.media.quadShader;
			trap_R_AddRefEntityToScene(ent);
		}
		if (state->powerups & (1 << PW_REGEN)) {
			if (((cg.time / 100) % 10) == 1) {
				ent->customShader = cgs.media.regenShader;
				trap_R_AddRefEntityToScene(ent);
			}
		}
		if (state->powerups & (1 << PW_BATTLESUIT)) {
			ent->customShader = cgs.media.battleSuitShader;
			trap_R_AddRefEntityToScene(ent);
		}
	}
}

/*
=================
CG_LightVerts
=================
 */
int CG_LightVerts(vec3_t normal, int numVerts, polyVert_t *verts) {
	int i, j;
	float incoming;
	vec3_t ambientLight;
	vec3_t lightDir;
	vec3_t directedLight;

	trap_R_LightForPoint(verts[0].xyz, ambientLight, directedLight, lightDir);

	for (i = 0; i < numVerts; i++) {
		incoming = DotProduct(normal, lightDir);
		if (incoming <= 0) {
			verts[i].modulate[0] = ambientLight[0];
			verts[i].modulate[1] = ambientLight[1];
			verts[i].modulate[2] = ambientLight[2];
			verts[i].modulate[3] = 255;
			continue;
		}
		j = (ambientLight[0] + incoming * directedLight[0]);
		if (j > 255) {
			j = 255;
		}
		verts[i].modulate[0] = j;

		j = (ambientLight[1] + incoming * directedLight[1]);
		if (j > 255) {
			j = 255;
		}
		verts[i].modulate[1] = j;

		j = (ambientLight[2] + incoming * directedLight[2]);
		if (j > 255) {
			j = 255;
		}
		verts[i].modulate[2] = j;

		verts[i].modulate[3] = 255;
	}
	return qtrue;
}

/*
===============
CG_Player
===============
 */
extern vmCvar_t cg_leiChibi;

void CG_Player(centity_t *cent) {
	clientInfo_t *ci;
	refEntity_t legs;
	refEntity_t torso;
	refEntity_t head;
	int clientNum;
	int renderfx;
	qboolean shadow;
	float shadowPlane;
	refEntity_t skull;
	refEntity_t powerup;
	int t;
	float c;
	float angle;
	vec3_t dir, angles;
	int camereyes = 0;
	// leilei - chibi hack
	float chibifactorbody = 0.0f;
	float chibifactortorso = 0.0f;
	float chibifactorhead = 0.0f;

	if (cg_leiChibi.integer > 0) {
		if (cg_leiChibi.integer == 1) {
			// chibi SD proportion
			chibifactortorso = 0.0f;
			chibifactorbody = 0.62f;
			chibifactorhead = 2.7f;
		} else if (cg_leiChibi.integer == 2) {
			// slightly younger proportion
			chibifactorbody = 0.92f;
			chibifactortorso = 0.82f;
			chibifactorhead = 1.30f;
		} else if (cg_leiChibi.integer == 3) {
			// slightly more 'real' proportion
			chibifactorbody = 0.92f;
			chibifactortorso = 0.97f;
			chibifactorhead = 0.92f;
		} else if (cg_leiChibi.integer == 4) {
			// big torso
			chibifactorbody = 0.85f;
			chibifactortorso = 1.3f;
			chibifactorhead = 0.91f;
		}
	} else {
		chibifactorbody = chibifactortorso = chibifactorhead = 0; // normal scale...
	}


	// the client number is stored in clientNum.  It can't be derived
	// from the entity number, because a single client may have
	// multiple corpses on the level using the same clientinfo
	clientNum = cent->currentState.clientNum;
	if (clientNum < 0 || clientNum >= MAX_CLIENTS) {
		CG_Error("Bad clientNum on player entity");
	}
	ci = &cgs.clientinfo[ clientNum ];

	// it is possible to see corpses from disconnected players that may
	// not have valid clientinfo
	if (!ci->infoValid) {
		return;
	}

	// get the player model information
	renderfx = 0;
	if (cent->currentState.number == cg.snap->ps.clientNum) {
		camereyes = 1; // it's me!
		if (!cg.renderingThirdPerson) {
			renderfx = RF_THIRD_PERSON; // only draw in mirrors
		} else {
			//		if (cg_cameraMode.integer) {
			//			return;
			//		}
		}
	}


	memset(&legs, 0, sizeof (legs));
	memset(&torso, 0, sizeof (torso));
	memset(&head, 0, sizeof (head));

	// get the rotation information
	CG_PlayerAngles(cent, legs.axis, torso.axis, head.axis);

	// get the animation state (after rotation, to allow feet shuffle)
	CG_PlayerAnimation(cent, &legs.oldframe, &legs.frame, &legs.backlerp,
			&torso.oldframe, &torso.frame, &torso.backlerp);

	// add the talk baloon or disconnect icon
	CG_PlayerSprites(cent);

	// add the shadow
	shadow = CG_PlayerShadow(cent, &shadowPlane);

	// add a water splash if partially in and out of water
	CG_PlayerSplash(cent);

	if (cg_shadows.integer == 3 && shadow) {
		renderfx |= RF_SHADOW_PLANE;
	}
	renderfx |= RF_LIGHTING_ORIGIN; // use the same origin for all
	if (cgs.gametype == GT_HARVESTER) {
		CG_PlayerTokens(cent, renderfx);
	}
	//
	// add the legs
	//
	legs.hModel = ci->legsModel;
	if (!ci->onepiece) {
		// onepiece uses internal model skins right now until skin loading is fixed later
		legs.customSkin = ci->legsSkin;
	}

	VectorCopy(cent->lerpOrigin, legs.origin);

	VectorCopy(cent->lerpOrigin, legs.lightingOrigin);

	// leilei - chibi mode hack
	if (chibifactorbody) {
		VectorScale(legs.axis[0], chibifactorbody, legs.axis[0]);
		VectorScale(legs.axis[1], chibifactorbody, legs.axis[1]);
		VectorScale(legs.axis[2], chibifactorbody, legs.axis[2]);
	}


	// leilei - q scale hack

	if (cg_enableQ.integer) {
		VectorScale(legs.axis[0], QUACK_SCALE, legs.axis[0]);
		VectorScale(legs.axis[1], QUACK_SCALE, legs.axis[1]);
		VectorScale(legs.axis[2], QUACK_SCALE, legs.axis[2]);
	}


	legs.shadowPlane = shadowPlane;
	legs.renderfx = renderfx;
	VectorCopy(legs.origin, legs.oldorigin); // don't positionally lerp at all

	if (cg_cameraEyes.integer) {
		legs.renderfx &= RF_FIRST_PERSON;
	}


	CG_AddRefEntityWithPowerups(&legs, &cent->currentState, ci->team, qfalse);

	// if the model failed, allow the default nullmodel to be displayed
	if (!legs.hModel) {
		return;
	}

	//
	// add the torso
	//
	if (ci->onepiece) {
		// skip all the further crap and put a head on the legs
		//
		// add the head
		//
		head.hModel = ci->headModel;

		if (!head.hModel) {
			return;
		}
		head.customSkin = ci->headSkin;

		VectorCopy(cent->lerpOrigin, head.lightingOrigin);

		CG_PositionRotatedEntityOnTag(&head, &legs, ci->legsModel, "tag_head");

		// 
		// add the eyes
		//



		if (camereyes) {
			cent->eyesOrigin[0] = head.origin[0];
			cent->eyesOrigin[1] = head.origin[1];
			cent->eyesOrigin[2] = head.origin[2];
			if (cg_cameraEyes.integer == 3) {
				vectoangles(head.axis[0], headang);
			} else {
				if (cg_cameraEyes.integer == 2) {
					vectoangles(head.axis[0], headang);
					//VectorCopy(cent->lerpAngles, headang);
					//headang[0] = cent->lerpAngles[0];
					headang[1] = cent->lerpAngles[1];
				} else {
					VectorCopy(cent->lerpAngles, headang);
				}
			}

			if (cg_cameraEyes.integer) {
				VectorCopy(head.origin, cent->eyesOrigin);

				VectorSubtract(cent->eyesOrigin, cent->lerpOrigin, cent->eyesOrigin);
				VectorCopy(cent->eyesOrigin, headpos);
			}
		}

		VectorCopy(cent->pe.eyepos, head.eyepos[0]); // Copy it to our refdef for the renderer

		// HMM
		{
			vec3_t v, forwaad;
			vec3_t orrg;
			trace_t trace;
			VectorCopy(cent->lerpAngles, v);
			AngleVectors(v, forwaad, NULL, NULL);
			VectorMA(cent->lerpOrigin, 1024, forwaad, v);
			VectorCopy(head.origin, orrg);
			CG_Trace(&trace, orrg, NULL, NULL, v, -1, CONTENTS_SOLID);
			if (trace.fraction < 1)
				VectorCopy(trace.endpos, v); // look closer
			VectorCopy(v, head.eyelook); // Copy it to our refdef for the renderer
		}

		head.shadowPlane = shadowPlane;

		head.renderfx = renderfx;

		if ((cg_cameraEyes.integer == 2) || (cg_cameraEyes.integer == 3)) {
			head.renderfx &= RF_FIRST_PERSON;
		}
		CG_AddRefEntityWithPowerups(&head, &cent->currentState, ci->team, qfalse);

		CG_BreathPuffs(cent, &head);

		CG_DustTrail(cent);
		return;
	}


	torso.hModel = ci->torsoModel;
	if (!torso.hModel) {
		return;
	}

	torso.customSkin = ci->torsoSkin;

	VectorCopy(cent->lerpOrigin, torso.lightingOrigin);

	CG_PositionRotatedEntityOnTag(&torso, &legs, ci->legsModel, "tag_torso");

	torso.shadowPlane = shadowPlane;
	torso.renderfx = renderfx;
	if (cg_cameraEyes.integer) {
		torso.renderfx &= RF_FIRST_PERSON;
	}


	if (chibifactortorso) {
		VectorScale(torso.axis[0], chibifactortorso, torso.axis[0]);
		VectorScale(torso.axis[1], chibifactortorso, torso.axis[1]);
		VectorScale(torso.axis[2], chibifactortorso, torso.axis[2]);
	}





	CG_AddRefEntityWithPowerups(&torso, &cent->currentState, ci->team, qfalse);

	if (cent->currentState.eFlags & EF_KAMIKAZE) {

		memset(&skull, 0, sizeof (skull));

		VectorCopy(cent->lerpOrigin, skull.lightingOrigin);
		skull.shadowPlane = shadowPlane;
		skull.renderfx = renderfx;

		if (cent->currentState.eFlags & EF_DEAD) {
			// one skull bobbing above the dead body
			angle = ((cg.time / 7) & 255) * (M_PI * 2) / 255;
			if (angle > M_PI * 2)
				angle -= (float) M_PI * 2;
			dir[0] = sin(angle) * 20;
			dir[1] = cos(angle) * 20;
			angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255;
			dir[2] = 15 + sin(angle) * 8;
			VectorAdd(torso.origin, dir, skull.origin);

			dir[2] = 0;
			VectorCopy(dir, skull.axis[1]);
			VectorNormalize(skull.axis[1]);
			VectorSet(skull.axis[2], 0, 0, 1);
			CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]);

			skull.hModel = cgs.media.kamikazeHeadModel;
			trap_R_AddRefEntityToScene(&skull);
			skull.hModel = cgs.media.kamikazeHeadTrail;
			trap_R_AddRefEntityToScene(&skull);
		} else {
			// three skulls spinning around the player
			angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255;
			dir[0] = cos(angle) * 20;
			dir[1] = sin(angle) * 20;
			dir[2] = cos(angle) * 20;
			VectorAdd(torso.origin, dir, skull.origin);

			angles[0] = sin(angle) * 30;
			angles[1] = (angle * 180 / M_PI) + 90;
			if (angles[1] > 360)
				angles[1] -= 360;
			angles[2] = 0;
			AnglesToAxis(angles, skull.axis);

			/*
			dir[2] = 0;
			VectorInverse(dir);
			VectorCopy(dir, skull.axis[1]);
			VectorNormalize(skull.axis[1]);
			VectorSet(skull.axis[2], 0, 0, 1);
			CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]);
			 */

			skull.hModel = cgs.media.kamikazeHeadModel;
			trap_R_AddRefEntityToScene(&skull);
			// flip the trail because this skull is spinning in the other direction
			VectorInverse(skull.axis[1]);
			skull.hModel = cgs.media.kamikazeHeadTrail;
			trap_R_AddRefEntityToScene(&skull);

			angle = ((cg.time / 4) & 255) * (M_PI * 2) / 255 + M_PI;
			if (angle > M_PI * 2)
				angle -= (float) M_PI * 2;
			dir[0] = sin(angle) * 20;
			dir[1] = cos(angle) * 20;
			dir[2] = cos(angle) * 20;
			VectorAdd(torso.origin, dir, skull.origin);

			angles[0] = cos(angle - 0.5 * M_PI) * 30;
			angles[1] = 360 - (angle * 180 / M_PI);
			if (angles[1] > 360)
				angles[1] -= 360;
			angles[2] = 0;
			AnglesToAxis(angles, skull.axis);

			/*
			dir[2] = 0;
			VectorCopy(dir, skull.axis[1]);
			VectorNormalize(skull.axis[1]);
			VectorSet(skull.axis[2], 0, 0, 1);
			CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]);
			 */

			skull.hModel = cgs.media.kamikazeHeadModel;
			trap_R_AddRefEntityToScene(&skull);
			skull.hModel = cgs.media.kamikazeHeadTrail;
			trap_R_AddRefEntityToScene(&skull);

			angle = ((cg.time / 3) & 255) * (M_PI * 2) / 255 + 0.5 * M_PI;
			if (angle > M_PI * 2)
				angle -= (float) M_PI * 2;
			dir[0] = sin(angle) * 20;
			dir[1] = cos(angle) * 20;
			dir[2] = 0;
			VectorAdd(torso.origin, dir, skull.origin);

			VectorCopy(dir, skull.axis[1]);
			VectorNormalize(skull.axis[1]);
			VectorSet(skull.axis[2], 0, 0, 1);
			CrossProduct(skull.axis[1], skull.axis[2], skull.axis[0]);

			skull.hModel = cgs.media.kamikazeHeadModel;
			trap_R_AddRefEntityToScene(&skull);
			skull.hModel = cgs.media.kamikazeHeadTrail;
			trap_R_AddRefEntityToScene(&skull);
		}
	}

	if (cent->currentState.powerups & (1 << PW_GUARD)) {
		memcpy(&powerup, &torso, sizeof (torso));
		powerup.hModel = cgs.media.guardPowerupModel;
		powerup.frame = 0;
		powerup.oldframe = 0;
		powerup.customSkin = 0;
		trap_R_AddRefEntityToScene(&powerup);
	}
	if (cent->currentState.powerups & (1 << PW_SCOUT)) {
		memcpy(&powerup, &torso, sizeof (torso));
		powerup.hModel = cgs.media.scoutPowerupModel;
		powerup.frame = 0;
		powerup.oldframe = 0;
		powerup.customSkin = 0;
		trap_R_AddRefEntityToScene(&powerup);
	}
	if (cent->currentState.powerups & (1 << PW_DOUBLER)) {
		memcpy(&powerup, &torso, sizeof (torso));
		powerup.hModel = cgs.media.doublerPowerupModel;
		powerup.frame = 0;
		powerup.oldframe = 0;
		powerup.customSkin = 0;
		trap_R_AddRefEntityToScene(&powerup);
	}
	if (cent->currentState.powerups & (1 << PW_AMMOREGEN)) {
		memcpy(&powerup, &torso, sizeof (torso));
		powerup.hModel = cgs.media.ammoRegenPowerupModel;
		powerup.frame = 0;
		powerup.oldframe = 0;
		powerup.customSkin = 0;
		trap_R_AddRefEntityToScene(&powerup);
	}
	if (cent->currentState.powerups & (1 << PW_INVULNERABILITY)) {
		if (!ci->invulnerabilityStartTime) {
			ci->invulnerabilityStartTime = cg.time;
		}
		ci->invulnerabilityStopTime = cg.time;
	} else {
		ci->invulnerabilityStartTime = 0;
	}
	if ((cent->currentState.powerups & (1 << PW_INVULNERABILITY)) ||
			cg.time - ci->invulnerabilityStopTime < 250) {

		memcpy(&powerup, &torso, sizeof (torso));
		powerup.hModel = cgs.media.invulnerabilityPowerupModel;
		powerup.customSkin = 0;
		// always draw
		powerup.renderfx &= ~RF_THIRD_PERSON;
		VectorCopy(cent->lerpOrigin, powerup.origin);

		if (cg.time - ci->invulnerabilityStartTime < 250) {
			c = (float) (cg.time - ci->invulnerabilityStartTime) / 250;
		} else if (cg.time - ci->invulnerabilityStopTime < 250) {
			c = (float) (250 - (cg.time - ci->invulnerabilityStopTime)) / 250;
		} else {
			c = 1;
		}
		VectorSet(powerup.axis[0], c, 0, 0);
		VectorSet(powerup.axis[1], 0, c, 0);
		VectorSet(powerup.axis[2], 0, 0, c);
		trap_R_AddRefEntityToScene(&powerup);
	}

	t = cg.time - ci->medkitUsageTime;
	if (ci->medkitUsageTime && t < 500) {
		memcpy(&powerup, &torso, sizeof (torso));
		powerup.hModel = cgs.media.medkitUsageModel;
		powerup.customSkin = 0;
		// always draw
		powerup.renderfx &= ~RF_THIRD_PERSON;
		VectorClear(angles);
		AnglesToAxis(angles, powerup.axis);
		VectorCopy(cent->lerpOrigin, powerup.origin);
		powerup.origin[2] += -24 + (float) t * 80 / 500;
		if (t > 400) {
			c = (float) (t - 1000) * 0xff / 100;
			powerup.shaderRGBA[0] = 0xff - c;
			powerup.shaderRGBA[1] = 0xff - c;
			powerup.shaderRGBA[2] = 0xff - c;
			powerup.shaderRGBA[3] = 0xff - c;
		} else {
			powerup.shaderRGBA[0] = 0xff;
			powerup.shaderRGBA[1] = 0xff;
			powerup.shaderRGBA[2] = 0xff;
			powerup.shaderRGBA[3] = 0xff;
		}
		trap_R_AddRefEntityToScene(&powerup);
	}

	//
	// add the head
	//
	head.hModel = ci->headModel;
	if (!head.hModel) {
		return;
	}
	head.customSkin = ci->headSkin;

	VectorCopy(cent->lerpOrigin, head.lightingOrigin);

	CG_PositionRotatedEntityOnTag(&head, &torso, ci->torsoModel, "tag_head");

	// 
	// add the eyes
	//



	if (camereyes) {
		cent->eyesOrigin[0] = head.origin[0];
		cent->eyesOrigin[1] = head.origin[1];
		cent->eyesOrigin[2] = head.origin[2];
		if (cg_cameraEyes.integer == 2) {
			vectoangles(head.axis[0], headang);
		} else {
			VectorCopy(cent->lerpAngles, headang);
		}




		if (cg_cameraEyes.integer) {
			VectorCopy(head.origin, cent->eyesOrigin);

			VectorSubtract(cent->eyesOrigin, cent->lerpOrigin, cent->eyesOrigin);
			VectorCopy(cent->eyesOrigin, headpos);
		}
	}

	VectorCopy(cent->pe.eyepos, head.eyepos[0]); // Copy it to our refdef for the renderer

	// HMM
	{
		vec3_t v, forwaad;
		vec3_t orrg;
		trace_t trace;
		VectorCopy(cent->lerpAngles, v);
		AngleVectors(v, forwaad, NULL, NULL);
		VectorMA(cent->lerpOrigin, 1024, forwaad, v);
		VectorCopy(head.origin, orrg);
		CG_Trace(&trace, orrg, NULL, NULL, v, -1, CONTENTS_SOLID);
		if (trace.fraction < 1)
			VectorCopy(trace.endpos, v); // look closer
		VectorCopy(v, head.eyelook); // Copy it to our refdef for the renderer
	}

	head.shadowPlane = shadowPlane;
	head.renderfx = renderfx;


	// leilei - chibi mode hack
	if (chibifactorhead) {
		VectorScale(head.axis[0], chibifactorhead, head.axis[0]);
		VectorScale(head.axis[1], chibifactorhead, head.axis[1]);
		VectorScale(head.axis[2], chibifactorhead, head.axis[2]);
	}



	CG_AddRefEntityWithPowerups(&head, &cent->currentState, ci->team, qfalse);

	CG_BreathPuffs(cent, &head);

	CG_DustTrail(cent);

	//
	// add the gun / barrel / flash
	//
	CG_AddPlayerWeapon(&torso, NULL, cent, ci->team);

	// add powerups floating behind the player
	CG_PlayerPowerups(cent, &torso);
}


//=====================================================================

/*
===============
CG_ResetPlayerEntity

A player just came into view or teleported, so reset all animation info
===============
 */
void CG_ResetPlayerEntity(centity_t *cent) {
	cent->errorTime = -99999; // guarantee no error decay added
	cent->extrapolated = qfalse;

	CG_ClearLerpFrame(&cgs.clientinfo[ cent->currentState.clientNum ], &cent->pe.legs, cent->currentState.legsAnim);
	CG_ClearLerpFrame(&cgs.clientinfo[ cent->currentState.clientNum ], &cent->pe.torso, cent->currentState.torsoAnim);

	BG_EvaluateTrajectory(&cent->currentState.pos, cg.time, cent->lerpOrigin);
	BG_EvaluateTrajectory(&cent->currentState.apos, cg.time, cent->lerpAngles);

	VectorCopy(cent->lerpOrigin, cent->rawOrigin);
	VectorCopy(cent->lerpAngles, cent->rawAngles);

	memset(&cent->pe.legs, 0, sizeof ( cent->pe.legs));
	cent->pe.legs.yawAngle = cent->rawAngles[YAW];
	cent->pe.legs.yawing = qfalse;
	cent->pe.legs.pitchAngle = 0;
	cent->pe.legs.pitching = qfalse;

	memset(&cent->pe.torso, 0, sizeof ( cent->pe.legs));
	cent->pe.torso.yawAngle = cent->rawAngles[YAW];
	cent->pe.torso.yawing = qfalse;
	cent->pe.torso.pitchAngle = cent->rawAngles[PITCH];
	cent->pe.torso.pitching = qfalse;

	memset(&cent->pe.head, 0, sizeof ( cent->pe.head));
	cent->pe.head.yawAngle = cent->rawAngles[YAW];
	cent->pe.head.yawing = qfalse;
	cent->pe.head.pitchAngle = cent->rawAngles[PITCH];
	cent->pe.head.pitching = qfalse;



	if (cg_debugPosition.integer) {
		CG_Printf("%i ResetPlayerEntity yaw=%f\n", cent->currentState.number, cent->pe.torso.yawAngle);
	}
}

